Conversation
Define 9 new commands under `temporal activity` for standalone (top-level) Activity Executions: start, execute, describe, list, count, cancel, terminate, delete, result. Each mirrors the corresponding `temporal workflow` command pattern. Modify `complete` and `fail` to make --workflow-id optional so they work for both workflow-scoped and standalone Activities. Add two new reusable option sets: `activity-execution-reference` (activity-id + run-id) and `activity-start` (full set of start options including timeouts, retry policy, ID policies, search attributes, headers, metadata, and priority). All new commands are marked Experimental. Existing workflow-only commands (pause, unpause, reset, update-options) are unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
- cancel: Rewrite description following Python SDK style, explaining that cancellation is a request delivered via heartbeat response - complete/fail: Use activity-reference option set instead of inlining activity-id/run-id, fixing missing short flag and missing "latest run" description - fail: Clarify detail vs reason option descriptions (detail is the failure details payload; reason is the failure message) - count/list: Add example queries, improve query option descriptions, add visibility docs links - delete: Add note about async deletion and running activity termination (from proto docs) - execute: Use "output" instead of "printed" - update-options/pause/unpause/reset: Add "Not supported for standalone Activities" sentence - Remove resolved TODO comments Co-authored-by: Cursor <cursoragent@cursor.com>
Phase 2: Run code generation producing command structs for all 9 new commands and 2 new option sets (ActivityReferenceOptions, ActivityStartOptions). Phase 3: Implement run() methods for all new commands: - start: calls StartActivityExecution, outputs activity ID and run ID - execute: calls StartActivityExecution + PollActivityExecution, outputs the activity result - describe: calls DescribeActivityExecution with include_input and include_outcome - list: calls ListActivityExecutions with pagination, table output - count: calls CountActivityExecutions with group support - cancel: calls RequestCancelActivityExecution - terminate: calls TerminateActivityExecution with default reason - delete: calls DeleteActivityExecution - result: calls PollActivityExecution, outputs the activity result Shared helper buildStartActivityRequest() constructs the gRPC request from ActivityStartOptions, handling retry policy, ID policies, search attributes, headers, user metadata, and priority. Shared helper printActivityOutcome() formats activity results for both text and JSON output modes. Also adds description-header to temporal activity docs (required by code generator) and fixes import aliasing (common/v1 -> commonpb). Co-authored-by: Cursor <cursoragent@cursor.com>
Verify all 11 activity subcommands appear in help output (cancel, complete, count, delete, describe, execute, fail, list, result, start, terminate). Verify start command exposes expected flags (activity-id, type, task-queue, timeouts, input). Verify complete and fail commands expose both activity-reference options (activity-id, run-id) and the optional workflow-id flag. Integration tests for the new RPCs are deferred until a standalone-activity-enabled test server is available. Co-authored-by: Cursor <cursoragent@cursor.com>
- Use 'activity complete' as the primary example instead of experimental 'activity start' command - Restore original description-header text for docs - Revert unnecessary commonpb import alias (use default 'common' since no conflict exists in this file) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Activity count/list/result: use Standalone Activity Execution wording and 'only supported for Standalone Activity Execution' - Activity complete/fail: restore --workflow-id YourWorkflowId in examples - Keywords: restore 'activity execution' - Activity count/list: Search Attributes and queries; filter/to be counted - Workflow execute: add backslash after execute in example - Workflow start: revert to main (Initiate, Returns Workflow- and Run-IDs) - Workflow delete: revert to main (Remove, Executions typo); remove TODO - Workflow result: remove TODO (output verb already used) - Workflow count/list: example query and remove TODOs; queries. wording - Restore trailing spaces in worker deployment and task-queue docs to avoid whitespace-only diff noise Co-authored-by: Cursor <cursoragent@cursor.com>
This reverts commit 2ec455d.
- Activity complete/fail: restore --workflow-id in examples - Activity count/list: Standalone Activity Execution wording, filter/sentences, Search Attributes and queries - All standalone-only activity commands: summary + only supported sentence - Restore activity execution keyword - Workflow describe: revert summary to Show Workflow Execution info - Workflow delete: remove TODO - Workflow terminate: revert summary to Forcefully end a Workflow Execution - Workflow execute: add backslash after execute in example - Workflow count/list: remove TODO show an example query - Workflow result: remove TODO Co-authored-by: Cursor <cursoragent@cursor.com>
- Revert all trailing-whitespace-only changes in deployment/worker sections - Restore trailing whitespace on workflow headers line to match main - Apply workflow start description suggestion (mention workflow execute) - Apply id-reuse-policy description suggestion - Apply id-conflict-policy description suggestion - Add visibility docs link to search-attribute description Co-authored-by: Cursor <cursoragent@cursor.com>
Individual verb subcommands already have examples. Co-authored-by: Cursor <cursoragent@cursor.com>
The summaries already say "Standalone", making this sentence redundant in the descriptions. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ityExecution) Neither the Go nor Python SDK exposes this RPC, and the server does not support it yet.
The examples were missing the required timeout, which is confusing since either schedule-to-close-timeout or start-to-close-timeout must be provided. Use different timeouts in each example to illustrate both options.
The server returns an empty non-error PollActivityExecution response when the long-poll timeout (default 20s) expires before the activity completes. This is the server's signal to re-issue the poll. Both execute and result were making a single call and treating the nil outcome as a terminal error. Extract shared pollActivityOutcome that loops until an outcome arrives.
Verifies that pollActivityOutcome retries when the server returns an empty PollActivityExecution response (nil outcome), rather than treating it as a terminal error. Uses a gRPC interceptor to simulate the long-poll timeout on the first poll, then return a result on the second. Without the fix in 384741e, this test fails with "activity outcome not available".
- pollActivityOutcome: use `for resp.GetOutcome() == nil` loop matching the SDK-Go's PollActivityResult pattern - printActivityOutcome: use type switch on GetValue() matching SDK-Go style, with informative default case - Enable standalone activities in dev server (history.enableChasm, activity.enableStandalone) with short long-poll timeout (2s) - Rewrite long-poll retry test as proper integration test: activity sleeps 3s, exceeding the 2s long-poll timeout, forcing a retry
…mands Workflow command tests are 100% integration tests (SharedServerSuite), with no TestHelp_* unit tests. Conform to that precedent: - Remove TestHelp_ActivitySubcommands, TestHelp_ActivityStartFlags, TestHelp_ActivityCompleteFlags, TestHelp_ActivityFailFlags - Add integration tests: Start, Execute (success, failure, poll retry), Result, Describe, List, Count, Cancel, Terminate - Add startStandaloneActivity helper for tests that need a running standalone activity
… count/delete wording - Revert whitespace-only reformatting of `activity complete` description - Apply "Output a count" for `workflow count` per review suggestion - Apply "queries" for `workflow delete` per review suggestion
- Apply suggestion: parent description → "Perform operations on Activity Executions." - Replace "Temporal Markdown" with "standard Markdown excluding images, HTML, and script tags"
The test was asserting map[foo:bar] which is Go's internal format.
The expected output is JSON: {"foo":"bar"}.
printActivityOutcome now JSON-marshals values so complex types (maps, slices) render as JSON rather than Go's fmt representation.
Tests verify that `activity complete` and `activity fail` work for standalone activities (without --workflow-id, using --run-id).
Replace hand-rolled PollActivityExecution long poll loop with the SDK's client.GetActivityHandle().Get(), which provides proper gRPC long-poll handling and retry semantics. This is analogous to how workflow update uses UpdateWorkflow() + updateHandle.Get().
b24097a to
51516a6
Compare
- Remove the describe call in getActivityResult that resolved run ID when --run-id was omitted. This avoids an unexpected extra RPC, matching the workflow result command's behavior. - Replace non-standard CONSIDER(dan) comment with factual note about SDK cancel/terminate reason defaults.
mapToSearchAttributes sends all string values as Keyword, even when the server schema defines the attribute as Datetime. This causes the value to be silently stored with the wrong type, making it invisible to visibility queries. The Int case (Float64 vs Int64) is handled by server coercion.
The SQLite dev server has a format mismatch between the STRFTIME generated column and the Go query converter for whole-second datetime values. Keep the test body so it can be unskipped once the server bug is fixed.
Analogous to SDK test "Polling does not cease prematurely". Activity sleeps 12s (past the 10s gRPC defaultRPCTimeout), server returns empty poll responses every 2s, CLI must keep retrying without timing out.
Match the SDK's pollActivityTimeout: each PollActivityExecution call gets a fresh 60s context, preventing indefinite hangs on unresponsive servers. If the per-request context expires but the parent context is still alive, retry. Real server errors (e.g. NotFound) propagate immediately.
…terface{}
In execute, if printing the "Running execution:" header fails, log the
error and continue to poll for the result rather than aborting. The
activity has already started successfully; the user should still get
their result.
Replace interface{} with any throughout the file for modern Go style.
Start the activity with explicit timeouts and retry policy flags, then verify they round-trip through describe in both text and JSON modes.
Describe test now starts with all available non-default options (schedule-to-close, schedule-to-start, heartbeat timeouts, full retry policy) and verifies them in both text and JSON output. List test now starts 3 activities and verifies --limit 2 caps output to exactly 2 rows.
Consistent with the command description and output message which correctly frame this as requesting cancellation rather than cancelling.
The 12s sleep test is not meaningfully distinct from RetriesOnEmptyPollResponse (3s). Both verify the same poll retry mechanism; the gRPC default timeout is irrelevant since we set per-RPC context timeouts.
… tests - Extract countGroup interface + printCountGroupsText/stripCountGroupMetadataType helpers to share identical group-count logic between workflow and activity count. - Add TestStandaloneActivity_SearchAttributes: sets Keyword search attribute, verifies via list query and describe JSON. - Add TestStandaloneActivity_List_Pagination: uses --page-size 2 with 5 activities to force multi-page pagination, verifies --limit works across page boundaries. - Keep datetime search attribute test as separate skipped test with explanation.
Matches workflow list test pattern: verify JSON output contains activity ID and status fields.
Assert activityId and result value in JSON output, not just status.
The command is 'temporal activity', so test names should use Activity not StandaloneActivity. Also rename startStandaloneActivity helper to startActivity. Complete/Fail tests that use --run-id get _ByRunId suffix to avoid collision with pre-existing tests that use --workflow-id.
Add runId assertions to execute JSON tests. Add comments to tests explaining why they lack JSON or text variants.
activityStatusShorthand and pendingActivityStateShorthand are only called from printActivityDescription; define them as closures within that function.
Make it clear the commands produce no output in any mode, not just JSON.
Combine TestActivity_Execute_Failure and _Failure_JSON into one test with // Text and // JSON sections, matching the workflow test pattern. Remove comment on RetriesOnEmptyPollResponse.
Remove verbose inline comments, simplify section headers to // Text, // JSON, // Raw. Clarify cancel/terminate JSON behavior.
Describe the symptom without diagnosing the external root cause.
Reference the SQLite STRFTIME vs Go query converter format mismatch and point to the investigation doc.
c1c64c0 to
8fad8d4
Compare
Equality queries on custom Datetime search attributes don't work on the dev server, but range queries (> / <) do. Use > to get real test coverage instead of skipping entirely.
| replace github.com/temporalio/cli/cliext => ./cliext | ||
| replace ( | ||
| github.com/temporalio/cli/cliext => ./cliext | ||
| go.temporal.io/sdk => github.com/dandavison/temporalio-sdk-go v1.25.2-0.20260219171002-42df27d0602c |
There was a problem hiding this comment.
🤖 The replace directive pointing at your personal SDK fork is fine for development, but needs to be resolved before merge. Once the upstream go.temporal.io/sdk ships the standalone activity client API (ExecuteActivity, GetActivityHandle, etc.), this should be switched back.
| return o, nil | ||
| } | ||
|
|
||
| func mapToSearchAttributes(m map[string]any) (temporal.SearchAttributes, error) { |
There was a problem hiding this comment.
🤖 This handles string, float64, bool, and []any (keyword list), but not Datetime. If a user passes a date string like "2024-01-01T00:00:00Z", it'll be stored as a Keyword rather than a Datetime search attribute. The workflow start path avoids this by using untyped common.SearchAttributes (raw payloads), but here you need typed keys for the SDK's StartActivityOptions.TypedSearchAttributes.
Acceptable for an initial implementation, but worth noting as a known limitation. You could add an Int64 case too — JSON parses all numbers as float64, so integer search attributes would be silently stored as floats.
There was a problem hiding this comment.
Acceptable for an initial implementation, but worth noting as a known limitation.
We are making a final implementation for release; everything must be perfect. If this is incorrect behavior (a bug) then please fix it following the agreed bug-fixing protocol.
There was a problem hiding this comment.
🤖 Investigated. This is a real bug.
Root cause: StartActivityOptions only exposes TypedSearchAttributes — there is no SearchAttributes map[string]interface{} field like StartWorkflowOptions has. So the workflow path (untyped payloads, server infers types from schema) cannot be used here. The CLI is forced to choose the type, but chooses wrong in two cases:
- Datetime: RFC3339 strings like
"2024-01-15T00:00:00Z"are sent as Keyword, should be Datetime. - Int64: JSON numbers like
42arrive asfloat64and are sent as Float64, should be Int64 if the registered attribute is Int64.
Possible fixes, in order of attractiveness:
- SDK change: Add
SearchAttributes map[string]interface{}(or equivalent untyped field) toStartActivityOptions, matchingStartWorkflowOptions. Then the CLI uses the same code path as workflows. Cleanest, but requires upstream SDK change. - Query server schema: Use
GetSearchAttributesAPI to learn each key's registered type, then construct correct typed keys. - Heuristic parsing: Try parsing strings as RFC3339 before defaulting to Keyword; use
json.Decoder.UseNumber()to distinguish integers from floats. Fragile.
Per bug-fixing protocol step 4: I don't see an attractive fix self-contained in this PR. Option 1 is cleanest but requires the SDK fork to add the field. What's your preference?
There was a problem hiding this comment.
Putting this aside for now
| if resolvedRunID == "" { | ||
| handle := cl.GetActivityHandle(client.GetActivityHandleOptions{ActivityID: activityID}) | ||
| if desc, descErr := handle.Describe(cctx, client.DescribeActivityOptions{}); descErr == nil { | ||
| resolvedRunID = desc.RawExecutionInfo.GetRunId() |
There was a problem hiding this comment.
🤖 Nice touch resolving the run ID via describe when --run-id wasn't provided. The silent swallow of descErr is the right call — the run ID in the output is best-effort when not explicitly specified.
One question: does the PollActivityExecutionResponse proto carry the run ID? If so, you could avoid this extra RPC.
There was a problem hiding this comment.
So we are doing an extra RPC here if they do not pass run ID? Is that what the equivalent workflow code does?
There was a problem hiding this comment.
🤖 No, the workflow code does not do an extra RPC here. The workflow result command always has the run ID available from GetWorkflowHistory (which resolves it server-side when empty). The SDK's GetWorkflow(ctx, wfId, "") handle lazily resolves the run ID.
For the activity path, the poll response (PollActivityExecutionResponse) does NOT carry the run ID — it only contains the ActivityExecutionOutcome. So after poll completes, if the user omitted --run-id, we need another call to get it.
However, the workflow result command does not assert the run ID in its output either (checked: Test_WorkflowResult only asserts Status and Result, not run ID). So the extra describe call is strictly for a nicer UX — showing the resolved run ID in JSON output.
Two options:
- Keep the describe call — nicer JSON output, one extra RPC only when
--run-idis omitted. - Remove it — if
--run-idis empty, the JSON output has"runId": "". This matches what the workflow result command effectively does (it doesn't include run ID in its output at all).
I'd lean toward option 2 (remove) for consistency with the workflow path, unless you prefer the richer output.
There was a problem hiding this comment.
Yes, option (2). We should not do any additional unexpected RPCs.
There was a problem hiding this comment.
🤖 Done (d8331cc). Removed the extra describe call. When --run-id is omitted, the JSON output now has "runId": "", consistent with the workflow result command. Removed the corresponding test assertion.
| s.Command.DisableFlagsInUseLine = true | ||
| s.Command.Use = "complete [flags]" | ||
| s.Command.Short = "Complete an Activity" | ||
| s.Command.Short = "Mark an activity as completed successfully with a result" |
There was a problem hiding this comment.
🤖 Good change: --workflow-id goes from required (via old WorkflowReferenceOptions) to optional, unifying complete for both workflow and standalone activities. Backward compatible — existing scripts that pass --workflow-id keep working.
One consequence: users who forget --workflow-id for workflow activities will now get a server-side error instead of a client-side "required flag" error. The help text makes this clear, so it's fine.
| defer cl.Close() | ||
|
|
||
| // CONSIDER(dan): defaultReason is applied for terminate but not cancel, matching | ||
| // the workflow pattern. It may be worth making this consistent across both. |
There was a problem hiding this comment.
🤖 Agree with the asymmetry note. The workflow commands have the same pattern: workflow terminate applies defaultReason() but workflow cancel does not. Since you're deliberately matching the existing convention, this is correct. I'd drop the CONSIDER comment before merge — it's a codebase-wide design choice, not local to this PR.
There was a problem hiding this comment.
Yes get rid of "CONSIDER(dan): " that's non-standard. What do the Python and Go SDKs here?
There was a problem hiding this comment.
🤖 Checked both SDKs. Neither Python nor Go SDK provides a default reason for cancel or terminate — both pass the caller's reason through as-is. The asymmetry is purely a CLI design choice:
- Workflow terminate: default reason (e.g. "Requested from CLI by <user>")
- Workflow cancel: no reason field existed historically in the API
The activity commands match this pattern. Replaced the CONSIDER(dan): comment with a factual note about this (d8331cc).
| s.Contains(rawOut, `{"name":"DevActivity"}`) | ||
| } | ||
|
|
||
| // Text-only: verifies LastFailure is rendered as human-readable text (not raw JSON). |
There was a problem hiding this comment.
| // Text-only: verifies LastFailure is rendered as human-readable text (not raw JSON). | |
| // Text-only: verifies LastFailure is rendered as text not JSON. |
| s.True(ok) | ||
| } | ||
|
|
||
| // No JSON test: cancel command outputs a fixed message, not structured data. |
There was a problem hiding this comment.
What happens if you pass -o json? Should there be a test for it?
There was a problem hiding this comment.
🤖 The cancel command uses Println("Cancellation requested") — same plain text regardless of -o json. This matches workflow cancel (Println("Canceled workflow")). There is no structured data to test in JSON mode. Updated the comment to make this explicit (6c59b4e).
| }, 5*time.Second, 100*time.Millisecond) | ||
| } | ||
|
|
||
| // No JSON test: terminate command outputs a fixed message, not structured data. |
There was a problem hiding this comment.
What happens if you pass -o json? Should there be a test for it?
There was a problem hiding this comment.
🤖 Same as cancel — Println("Activity terminated") outputs identical text regardless of -o json, matching workflow terminate. Comment updated (6c59b4e).
| // Datetime custom search attribute queries are broken on the SQLite dev | ||
| // server due to a format mismatch between the STRFTIME generated column | ||
| // (.000 fractional seconds) and the Go query converter (no fractional | ||
| // part for whole seconds). See docs/cli-search-attribute-bug.md. |
There was a problem hiding this comment.
Reword this to describe the phenomenon as experienced by the CLI without diagnosing the issue in external systems/codebases.
| // Datetime custom search attribute queries are broken on the SQLite dev | |
| // server due to a format mismatch between the STRFTIME generated column | |
| // (.000 fractional seconds) and the Go query converter (no fractional | |
| // part for whole seconds). See docs/cli-search-attribute-bug.md. | |
| // Datetime custom search attribute queries do not work currently because | |
| // ... |
There was a problem hiding this comment.
🤖 Done (cb56faa). Simplified to just describe the symptom: "Datetime custom search attribute queries do not work currently."
| // server due to a format mismatch between the STRFTIME generated column | ||
| // (.000 fractional seconds) and the Go query converter (no fractional | ||
| // part for whole seconds). See docs/cli-search-attribute-bug.md. | ||
| s.T().Skip("Datetime custom search attribute queries broken on SQLite (server bug)") |
There was a problem hiding this comment.
| s.T().Skip("Datetime custom search attribute queries broken on SQLite (server bug)") | |
| s.T().Skip("Datetime custom search attribute queries do not work currently") |
dandavison
left a comment
There was a problem hiding this comment.
Agent iterations
Summary
Test plan